/*************************************************************************
 *
 *   file		: ObjectBase.Update.cs
 *   copyright		: (C) The WCell Team
 *   email		: info@wcell.org
 *   last changed	: $LastChangedDate: 2009-06-30 11:49:04 +0100 (Tue, 30 Jun 2009) $
 *   last author	: $LastChangedBy: dominikseifert $
 *   revision		: $Rev: 1042 $
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *************************************************************************/

using System;
using System.Collections.Generic;
using WCell.Constants;
using WCell.Constants.Updates;
using WCell.Core;
using WCell.Core.Network;
using WCell.RealmServer.UpdateFields;

namespace WCell.RealmServer.Entities
{
	public partial class ObjectBase
	{
		protected abstract UpdateType GetCreationUpdateType(UpdateFieldFlags relation);
		

		protected internal bool m_requiresUpdate;

		/// <summary> 
		/// This is a reference to <see cref="m_privateUpdateMask"/> if there are no private values in this object
		/// else its handled seperately
		/// </summary>
		protected internal UpdateMask m_publicUpdateMask;
		protected UpdateMask m_privateUpdateMask;
		protected internal int m_highestUsedUpdateIndex;

		public bool RequiresUpdate
		{
			get { return m_requiresUpdate; }
		}

		public abstract UpdateFieldHandler.DynamicUpdateFieldHandler[] DynamicUpdateFieldHandlers
		{
			get;
		}

		public void ResetUpdateInfo()
		{
			m_privateUpdateMask.Clear();
			if (m_privateUpdateMask != m_publicUpdateMask)
			{
				m_publicUpdateMask.Clear();
			}

			m_requiresUpdate = false;
		}

		/// <summary>
		/// Whether the given field is public
		/// </summary>
		/// <param name="fieldIndex"></param>
		/// <returns></returns>
		public bool IsUpdateFieldPublic(int fieldIndex)
		{
			return (_UpdateFieldInfos.FieldFlags[fieldIndex] & (UpdateFieldFlags.Public | UpdateFieldFlags.Dynamic)) != 0;
		}

		/// <summary>
		/// Whether this Object has any private Update fields
		/// </summary>
		/// <returns></returns>
		private bool HasPrivateUpdateFields
		{
			get { return _UpdateFieldInfos.HasPrivateFields; }
		}

		public virtual UpdateFieldFlags GetVisibilityFor(Character chr)
		{
			return UpdateFieldFlags.Public;
		}

		public abstract void RequestUpdate();
        

		#region Write Updates
		public void WriteObjectCreationUpdate(Character receiver)
		{
			var relation = GetVisibilityFor(receiver) | UpdateFieldFlags.Dynamic;
			receiver.m_updatePacket.Write((byte)GetCreationUpdateType(relation));
			EntityId.WritePacked(receiver.m_updatePacket);
			receiver.m_updatePacket.Write((byte)ObjectTypeId);

		    var updateFlags = UpdateFlags;
            if (receiver == this)
            {
                updateFlags |= UpdateFlags.Self;
            }

		    var packet = receiver.m_updatePacket;

		    packet.Write((ushort) updateFlags);

            /* In 3.1 the update packet changes slightly
             * the UpdateFlags are now 2bytes wide, because of the addition of flags 0x100 and 0x200
             * 0x100 - Stationary object on transport. Mututally exclusive with 0x20 and 0x40. Order checked -> 0x20 > 0x100 > 0x40
             *       - Contents: PackedGuid transportGuid, Vector3 position, Vector3 transportPosition, float facing, float transportFacing
             *       
             * 0x200 - Unknown use
             *       - Contents: int64 - old rotation field from GameObjects
             */

            if (updateFlags.Has(UpdateFlags.Living | UpdateFlags.StationaryObject | UpdateFlags.StationaryObjectOnTransport))
            {
                WriteMovementUpdate(packet, relation);
            }
		    if (updateFlags.Has(UpdateFlags.Flag_0x8))
            {
                WriteUpdateFlag_0x8(packet, relation);
            }
            if (updateFlags.Has(UpdateFlags.Flag_0x10))
            {
                WriteUpdateFlag_0x10(packet, relation);
            }

		    WriteTypeSpecificMovementUpdate(packet, relation, updateFlags);

			WriteUpdateValues(true, receiver, relation);

			receiver.UpdateCount++;
		}

        protected virtual void WriteUpdateFlag_0x8(PrimitiveWriter writer, UpdateFieldFlags relation)
        {
            writer.Write(EntityId.LowRaw);
        }

        protected virtual void WriteUpdateFlag_0x10(PrimitiveWriter writer, UpdateFieldFlags relation)
        {
            writer.Write(1);
        }

        protected virtual void WriteTypeSpecificMovementUpdate(PrimitiveWriter writer, UpdateFieldFlags relation, UpdateFlags updateFlags)
        {
        }

        /// <summary>
        /// Writes the major portion of the create block.
        /// This handles flags 0x20, 0x40, and 0x100, they are exclusive to each other
        /// The content depends on the object's type
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="relation"></param>
        protected virtual void WriteMovementUpdate(PrimitiveWriter writer, UpdateFieldFlags relation)
        {
        }

		public void WriteObjectValueUpdate(Character receiver)
		{
			// TODO: Find a better way to keep track of changed Dynamic UpdateFields
			var relation = GetVisibilityFor(receiver) | UpdateFieldFlags.Dynamic;

			receiver.m_updatePacket.Write((byte)UpdateType.Values);
			EntityId.WritePacked(receiver.m_updatePacket);
			WriteUpdateValues(false, receiver, relation);

			receiver.UpdateCount++;
		}

		protected void WriteUpdateValues(bool forCreation, Character receiver, UpdateFieldFlags relation)
		{
			UpdateMask mask;
			if (forCreation)
			{
				// completely new
				var pos = receiver.m_updatePacket.Position;
				mask = new UpdateMask(m_highestUsedUpdateIndex);
				receiver.m_updatePacket.Position = pos + 1 + (4 * mask.MaxBlockCount);	// skip over the index block
				for (var i = 0; i <= m_highestUsedUpdateIndex; i++)
				{
					var flags = _UpdateFieldInfos.FieldFlags[i];
					if ((flags & relation) != 0 && m_updateValues[i].UInt32 != 0)
					{
						mask.SetBit(i);
						WriteUpdateValue(receiver.m_updatePacket, receiver, i);
					}
				}
				var newPos = receiver.m_updatePacket.Position;
				receiver.m_updatePacket.Position = pos;
				mask.WriteFull(receiver.m_updatePacket);								// write the full index block
				receiver.m_updatePacket.Position = newPos;
				return;
				//WriteUpdateValues(receiver, relation, 0, _UpdateFieldInfos.Fields.Length, true);
			}

		    if ((relation & UpdateFieldFlags.Private) != 0)
		    {
		        // Private
		        mask = m_privateUpdateMask;
		    }
		    else if ((relation & (UpdateFieldFlags.OwnerOnly | UpdateFieldFlags.GroupOnly)) != 0)
		    {
		        // Group or Owner
		        var pos = receiver.m_updatePacket.Position;
		        mask = new UpdateMask(m_privateUpdateMask.m_highestIndex);
		        receiver.m_updatePacket.Position = pos + 1 + (4 * mask.MaxBlockCount);	// skip over the index block
		        for (var i = m_privateUpdateMask.m_lowestIndex; i <= m_privateUpdateMask.m_highestIndex; i++)
		        {
		            var flags = _UpdateFieldInfos.FieldFlags[i];
		            if ((flags & relation) != 0 && 
						((flags & UpdateFieldFlags.Public) == 0 || m_publicUpdateMask.GetBit(i)))
		            {
		                mask.SetBit(i);
		                WriteUpdateValue(receiver.m_updatePacket, receiver, i);
		            }
		        }
		        var newPos = receiver.m_updatePacket.Position;
		        receiver.m_updatePacket.Position = pos;
		        mask.WriteFull(receiver.m_updatePacket);								// write the full index block
		        receiver.m_updatePacket.Position = newPos;
		        //WriteUpdateValues(receiver, relation, m_privateUpdateMask.m_lowestIndex, m_privateUpdateMask.m_highestIndex, false);
		        return;
		    }
		    else
		    {
		        // Public
		        mask = m_publicUpdateMask;
		    }

		    mask.WriteTo(receiver.m_updatePacket);

		    for (var i = mask.m_lowestIndex; i <= mask.m_highestIndex; i++)
		    {
		        if (mask.GetBit(i))
		        {
		            WriteUpdateValue(receiver.m_updatePacket, receiver, i);
		        }
		    }
		}

		protected void WriteUpdateValue(UpdatePacket packet, Character receiver, int index)
		{
			if (_UpdateFieldInfos.FieldFlags[index] == UpdateFieldFlags.Dynamic)
			{
				DynamicUpdateFieldHandlers[index](this, receiver, packet);
			}
			else
			{
				packet.Write(m_updateValues[index].UInt32);
			}
		}

		public void WriteSpontaneousOwnerUpdate(Character owner, params UpdateFieldId[] indices)
		{
			WriteSpontaneousOwnerUpdate(owner, true, indices);
		}

		public void WriteSpontaneousOwnerUpdate(Character owner, bool visible, params UpdateFieldId[] indices)
		{
			var highestIndex = 0;
			foreach (var index in indices)
			{
				if (index.RawId > highestIndex)
				{
					highestIndex = index.RawId;
				}
			}

			var mask = new UpdateMask(highestIndex);
			using (var packet = new UpdatePacket(1024))
			{
				packet.Position = 4;
				packet.Write(1);
				packet.Write((byte)UpdateType.Values);
				EntityId.WritePacked(packet);
				WriteSpontaneousUpdate(mask, packet, owner, indices, visible);
				owner.Send(packet);
			}
		}

		protected void WriteSpontaneousUpdate(UpdateMask mask, UpdatePacket packet, Character receiver, UpdateFieldId[] indices, bool visible)
		{
            for (var i = 0; i < indices.Length; i++)
            {
            	var index = indices[i].RawId;
            	var field = UpdateFieldMgr.Get(ObjectTypeId).Fields[index];
				for (var j = 0; j < field.Size; j++)
				{
					mask.SetBit(index + j);
				}
            }

			mask.WriteTo(packet);

			for (var i = mask.m_lowestIndex; i <= mask.m_highestIndex; i++)
			{
				if (mask.GetBit(i))
				{
					if (visible)
					{
						packet.Write(m_updateValues[i].UInt32);
					}
					else
					{
						packet.Write(0);
					}
				}
			}
		}

		#endregion

		public RealmPacketOut CreateDestroyPacket()
		{
			var packet = new RealmPacketOut(RealmServerOpCode.SMSG_DESTROY_OBJECT, 9);
			packet.Write(EntityId);

			packet.Write((byte)0); // NEW 3.0.2

			return packet;
		}

		public void SendDestroyToPlayer(Character c)
		{
			using (var packet = CreateDestroyPacket())
			{
				c.Client.Send(packet);
			}
		}

		// TODO: Improve (a lot)

		#region Spontaneous UpdateBlock Creation

		protected UpdatePacket GetFieldUpdatePacket(UpdateFieldId field, uint value)
		{
			var blocks = (field.RawId >> 5) + 1;
			var emptyBlockSize = (blocks - 1) * 4;

			//UpdatePacket packet = new UpdatePacket(BufferManager.Small.CheckOut());
			var packet = new UpdatePacket { Position = 4 };

			packet.Write(1); // Update Count
			packet.Write((byte)UpdateType.Values);

			EntityId.WritePacked(packet);

			packet.Write((byte)blocks);

			//packet.TotalLength += emptyBlockSize;
			packet.Zero(emptyBlockSize);

			packet.Write(1 << (field.RawId & 31));
			packet.Write(value);

			return packet;
		}

		protected UpdatePacket GetFieldUpdatePacket(UpdateFieldId field, byte[] value)
		{
			var blocks = (field.RawId >> 5) + 1;
			var emptyBlockSize = (blocks - 1) * 4;

			//UpdatePacket packet = new UpdatePacket(BufferManager.Small.CheckOut());
			var packet = new UpdatePacket { Position = 4 };

			packet.Write(1); // Update Count
			packet.Write((byte)UpdateType.Values);

			EntityId.WritePacked(packet);

			packet.Write((byte)blocks);

			//packet.TotalLength += emptyBlockSize;
			packet.Zero(emptyBlockSize);

			packet.Write(1 << (field.RawId & 31));
			packet.Write(value);

			return packet;
		}

		protected UpdatePacket GetFieldUpdatePacket(UpdateFieldId field, EntityId value)
		{
			//UpdatePacket packet = new UpdatePacket(BufferManager.Small.CheckOut());
			var packet = new UpdatePacket(128) { Position = 4 };

			packet.Write(1); // Update Count
			packet.Write((byte)UpdateType.Values);

			EntityId.WritePacked(packet);

			var blocks = (byte)((field.RawId + 1) / 32 + 2);
			packet.Write(blocks);

			if (blocks > 1)
			{
				packet.Zero((blocks - 2) * 4);

				var updateBlocks = new int[blocks];

				updateBlocks[field.RawId << 5] = (1 << (field.RawId & 31));
				updateBlocks[field.RawId + 1 << 5] = (1 << (field.RawId + 1 & 31));

				packet.Write(updateBlocks[0]);
				packet.Write(updateBlocks[1]);
			}
			else
			{
				packet.Zero((blocks - 1) * 4);
				packet.Write((1 << (field.RawId & 31) | 1 << (field.RawId + 1 & 31)));
			}

			packet.Write(value);

			return packet;
		}

		public void PushFieldUpdateToPlayer(Character character, UpdateFieldId field, int value)
		{
			using (var packet = GetFieldUpdatePacket(field, (uint)value))
			{
				SendUpdatePacket(character, packet);
			}
		}

		public void PushFieldUpdateToPlayer(Character character, UpdateFieldId field, uint value)
		{
			using (var packet = GetFieldUpdatePacket(field, value))
			{
				SendUpdatePacket(character, packet);
			}
		}

		public void PushFieldUpdateToPlayer(Character character, UpdateFieldId field, byte[] value)
		{
			using (var packet = GetFieldUpdatePacket(field, value))
			{
				SendUpdatePacket(character, packet);
			}
		}

	    protected static void SendUpdatePacket(Character character, UpdatePacket packet)
		{
			packet.SendTo(character.Client);
		}

	    #endregion
	}
}